import { ui } from "./ui";
export namespace Chat {
  export enum MessageType {
    text = 'text',
    img = 'img',
    customBubble = 'customBubble',
    customBody = 'customRow',
    emoticon = 'emoticon',
    system = 'system',
    file = 'file'
  }
  export interface AImg {
    width: number;
    height: number;
    type: string;
    size: number;
    url: string;
  }
  export interface AFile {
    id?: string;
    type?: string;
    name?: string;
    size?: number;
    url?: string;
    icon?: string;
  }
  export interface Sender {
    id?: string;
    type?: string;
    name?: string;
    head?: string;
  }
  export interface Message {
    type: MessageType;
    id: string;
    content: string;
    isSelf: boolean;
    timestamp: number;
    sender?: Sender;
    file?: AFile;
    image?: AImg;
  }

  export type CssClsNameGroup = string | [string, string];

  export enum ElementEnum {
    imgBubble = 'imgBubble',
    img = 'img',
    textBubble = 'textBubble',
    emoticon = 'emoticon',
    senderName = 'senderName',
    senderHead = 'senderHead',
    bodyZone = 'bodyZone',
    systemBody = 'systemBody',
    fileBubble = 'fileBubble',
    fileIcon = 'fileIcon',
    fileName = 'fileName',
    fileInfo = 'fileInfo',
    fileSize = 'fileSize'
  };

  export const cssNames: Record<ElementEnum, CssClsNameGroup> = {
    [ElementEnum.fileInfo]: [
      'nsb_chatview_my_msg_file_info',
      'nsb_chatview_od_msg_file_info'
    ],
    [ElementEnum.fileIcon]: [
      'nsb_chatview_my_msg_file_icon',
      'nsb_chatview_od_msg_file_icon'
    ],
    [ElementEnum.emoticon]: [
      'nsb_chatview_my_emoticon',
      'nsb_chatview_od_emoticon'
    ],
    [ElementEnum.imgBubble]: [
      'nsb_chatview_my_msg_img_bubble',
      'nsb_chatview_od_msg_img_bubble'
    ],
    [ElementEnum.fileBubble]: [
      'nsb_chatview_my_msg_file_bubble',
      'nsb_chatview_od_msg_file_bubble'
    ],
    [ElementEnum.fileName]: [
      'nsb_chatview_my_msg_file_name',
      'nsb_chatview_od_msg_file_name'
    ],
    [ElementEnum.fileSize]: [
      'nsb_chatview_my_msg_file_size',
      'nsb_chatview_od_msg_file_size'
    ],
    [ElementEnum.img]: [
      'nsb_chatview_my_msg_img',
      'nsb_chatview_od_msg_img'
    ],
    [ElementEnum.textBubble]: [
      'nsb_chatview_my_msg_text_bubble',
      'nsb_chatview_od_msg_text_bubble'
    ],
    [ElementEnum.senderName]: [
      'nsb_chatview_my_msg_sender_name',
      'nsb_chatview_od_msg_sender_name'
    ],
    [ElementEnum.senderHead]: [
      'nsb_chatview_my_msg_sender_head',
      'nsb_chatview_od_msg_sender_head'
    ],
    [ElementEnum.bodyZone]: [
      'nsb_chatview_my_msg_body_zone',
      'nsb_chatview_od_msg_body_zone'
    ],
    [ElementEnum.systemBody]: "nsb_chatview_msg_system_body"
  }

  function findCssName(msg: Message, group: CssClsNameGroup) {
    return Array.isArray(group) ? group[msg.isSelf ? 0 : 1] : group
  }
  function formatDate(timestamp: number): string {
    return new Date(timestamp).toUTCString();
  }
  function formatFileSize(bytes: any): string {
    if (typeof bytes !== 'number') {
      return '';
    } else if (bytes < 1024) {
      return `${bytes.toFixed(2)}b`;
    } else if (bytes < 1048576) {
      return `${(bytes / 1024).toFixed(2)}kb`;
    } else if (bytes < 1073741824) {
      return `${(bytes / 1048576).toFixed(2)}mb`;
    } else {
      return `${(bytes / 1073741824).toFixed(2)}gb`
    }
  }
  interface MsgElement extends HTMLDivElement {
    nameEle: HTMLDivElement;
    msg: Message;
  }
  export class MessageListView {
    renderCustomBody?: (msg: Message) => ui.UINode;
    renderCustomBubble?: (msg: Message) => ui.UINode;
    renderInHead?: (msg: Message) => ui.UINode;
    onClickHead?: (msg: Message) => void;
    onClickImg?: (msg: Message) => void;
    onClickFile?: (msg: Message) => void;
    get container() { return this._container }
    get messages() { return this.getMessages(); }
    set messages(value) { this.setMessages(value); }
    get nameVisible() { return this._nameVisible; }
    set nameVisible(value) {
      this._nameVisible = value;
      const arr = this.container.childNodes;
      const len = arr.length;
      for (let i = 0; i < len; ++i) {
        const { nameEle, msg } = (arr[i] as any)
        nameEle.innerText = value ? msg.senderName : ''
      }
    }
    get headClickable() {
      return this._headClickable;
    }
    set headClickable(value) { this._headClickable = value; }

    protected _messages: Message[] = [];
    protected _nameVisible = false;
    protected _headClickable = true;
    protected _parent: HTMLElement;
    protected _container: HTMLDivElement;
    constructor(parent: HTMLElement) {
      this._parent = parent;
      this._container = ui.node('div', {})
      this._parent.appendChild(this._container)

    }

    getMessages(): Message[] {
      return this._messages;
    }

    setMessages(items: Message[]): void {
      this._messages = items;
      this._container.innerHTML = ''
      this.applyNewMessages(null, 0, this._messages.length)
    }

    removeMessages(messages: Message[]): void {
      messages.map(msg => {
        const idx = this._messages.indexOf(msg)
        return this._container.childNodes[idx]
      }).map(node => {
        node && this._container.removeChild(node);
      });
    }

    appendMessages(messages: Message[]): void {
      const { scrollTop, clientHeight, scrollHeight } = this._container
      const prevLen = this._messages.length;
      this._messages.push(...messages);
      this.applyNewMessages(null, prevLen, this._messages.length)
      if (scrollTop + clientHeight === scrollHeight)
        this._container.scrollTo({ top: this._container.scrollHeight });
    }

    insertMessages(index: number, messages: Message[]): void {
      this._messages.splice(index, 0, ...messages);
      const { scrollTop, scrollHeight } = this._container;
      const anchor = this._container.childNodes[index];
      this.applyNewMessages(anchor, index, messages.length)
      this._container.scrollTo({ top: scrollTop + this._container.scrollHeight - scrollHeight })
    }

    updateMessage(index: number, message: Message): void {
      const node = this._container.childNodes[index];
      const ele = this.createMsgRow({ prevMsg: this._messages[index - 1], msg: message })
      this._container.insertBefore(ele, node)
      this._container.removeChild(node);
    }

    protected getNodeId(msg: Message) {
      return 'msg_row_' + msg.id
    }

    protected applyNewMessages(anchor: Node | null, from: number, to: number) {
      const arr = this._messages;
      while (from < to) {
        const msg = arr[from++]
        const node = this.createMsgRow({ prevMsg: arr[from - 2], msg })
        this.container.insertBefore(node, anchor);
      }
    }

    protected createMsgBody(msg: Message, nameEle: HTMLDivElement) {
      switch (msg.type) {
        case MessageType.customBody: {
          return this.renderCustomBody && this.renderCustomBody(msg)
        }
        case MessageType.system: {
          return ui.node('div', {
            className: findCssName(msg, cssNames[ElementEnum.systemBody])
          }, msg.content)
        }
        default: {
          return ui.node('div', {
            className: findCssName(msg, cssNames[ElementEnum.bodyZone])
          }, [
            ui.node('button', {
              disabled: !this._headClickable,
              className: findCssName(msg, cssNames[ElementEnum.senderHead]),
              onclick: () => this.onClickHead?.call(this, msg)
            }, [
              ui.node('img', { src: msg.sender?.head }),
              this.renderInHead && this.renderInHead(msg)
            ]),
            ui.node('div', {
              className: 'nsb_chatview_msg_bubble_zone'
            }, [
              nameEle,
              this.createMsgBubble(msg)
            ])
          ])
        }
      }
    }

    protected createMsgBubble(msg: Message) {
      switch (msg.type) {
        case MessageType.emoticon:
          return ui.node('img', {
            className: findCssName(msg, cssNames[ElementEnum.emoticon]),
            src: msg.content
          })
        case MessageType.text:
          return ui.node('div', {
            className: findCssName(msg, cssNames[ElementEnum.textBubble]),
          }, msg.content)
        case MessageType.img:
          return ui.node('button', {
            className: findCssName(msg, cssNames[ElementEnum.imgBubble]),
            onclick: () => this.onClickImg && this.onClickImg(msg)
          }, [
            ui.node('img', {
              className: findCssName(msg, cssNames[ElementEnum.img]),
              src: msg.content
            })
          ])
        case MessageType.file:
          return ui.node('button', {
            className: findCssName(msg, cssNames[ElementEnum.fileBubble]),
            onclick: () => this.onClickFile && this.onClickFile(msg)
          }, [
            ui.node('img', {
              className: findCssName(msg, cssNames[ElementEnum.fileIcon]),
              src: msg.file?.icon
            }),
            ui.node('div', {
              className: findCssName(msg, cssNames[ElementEnum.fileInfo])
            }, [
              ui.node('div', {
                className: findCssName(msg, cssNames[ElementEnum.fileName]),
              }, msg.file?.name),
              ui.node('div', {
                className: findCssName(msg, cssNames[ElementEnum.fileSize]),
              }, formatFileSize(msg.file?.size))
            ])
          ])
        case MessageType.customBubble: {
          const ret = this.renderCustomBubble && this.renderCustomBubble(msg);
          return ret;
        }
      }
    }

    protected createMsgRow(props: { prevMsg?: Message, msg: Message }): HTMLDivElement {
      const { prevMsg, msg } = props;

      const timeDiff = msg.timestamp - (prevMsg?.timestamp || 0)
      const nameEle = ui.node('div', {
        className: findCssName(msg, cssNames[ElementEnum.senderName])
      }, (this._nameVisible && msg.sender?.name) ? msg.sender.name : '');

      let bodyEle = this.createMsgBody(msg, nameEle);
      const ret = <MsgElement>ui.node('div', {
        id: this.getNodeId(msg),
        className: 'nsb_chatview_msg_root',
      }, [
        ui.node('div', {
          className: 'nsb_chatview_msg_date'
        }, timeDiff < 1000 ? '' : formatDate(msg.timestamp)),
        bodyEle
      ]);

      ret.nameEle = nameEle;
      ret.msg = msg;
      setTimeout(() => {
        ret.style.opacity = '1';
        ret.style.transition = 'all 1000ms'
      }, 1);
      return ret;
    }
  }
}